using System.Collections.Generic;
using System.Diagnostics;

namespace rsync
{
    /// <summary>
    /// 玩家终端，一个中端一个玩家、多玩家一个设备
    /// </summary>
    public class Device
    {
        #region Enum

        /// <summary>
        /// 终端类型，远程终端还是本机
        /// </summary>
        public enum DeviceType
        {
            Local,
            Remote,
            Spectator
        }

        /// <summary>
        /// 终端状态：运行中，断开连接，同步中
        /// </summary>
        public enum DeviceState
        {
            Syncing,
            Running,
            Disconnected,
        }

        #endregion

        #region Public

        /// <summary>
        /// 终端ID
        /// </summary>
        public readonly uint Id;
        /// <summary>
        /// 该终端的玩家数量，通常一个或者两个
        /// </summary>
        public readonly uint PlayerCount;
        /// <summary>
        /// 终端类型，远程终端还是本机
        /// </summary>
        public readonly DeviceType Type;

        /// <summary>
        /// 远程终端的帧数
        /// </summary>
        public int RemoteFrame;
        
        /// <summary>
        /// 终端超前的帧数
        /// </summary>
        public int RemoteFrameAdvantage;
        /// <summary>
        /// 最后一个被确认的帧数
        /// </summary>
        public uint LastAckedInputFrame;

        /// <summary>
        /// 终端状态：运行中，断开连接，同步中
        /// </summary>
        public DeviceState State;

        #endregion

        #region Private

        private const uint NUM_SYNC_ROUNDTRIPS = 5;
        private const uint SYNC_NEXT_RETRY_INTERVAL = 2000;
        private const uint SYNC_FIRST_RETRY_INTERVAL = 500;

        /// <summary>
        /// 终端通信会话对象
        /// </summary>
        private readonly Session _session;

        private uint _lastSendTime;
        private uint _syncRoundtripsRemaining;
        private ushort _syncRoundtripsRandomRequest;
        /// <summary>
        /// 消息队列
        /// </summary>
        private Queue<DeviceMessageQueueEntry> _sendQueue;

        #endregion

        /// <summary>
        /// 构建一个终端对象
        /// </summary>
        /// <param name="session">终端的通信会话对象</param>
        /// <param name="deviceId">该终端的ID</param>
        /// <param name="playerCount">该终端的玩家数量</param>
        /// <param name="deviceType">该终端的类型，远端或本机</param>
        public Device(Session session, uint deviceId, uint playerCount, DeviceType deviceType)
        {
            _session = session;
            _sendQueue = new Queue<DeviceMessageQueueEntry>();

            Id = deviceId;
            Type = deviceType;
            PlayerCount = playerCount;

            State = deviceType == DeviceType.Local ? DeviceState.Running : DeviceState.Syncing;
            RemoteFrame = 0;
            RemoteFrameAdvantage = 0;
        }

        public override string ToString()
        {
            return $"Device {new { Id, PlayerCount }}";
        }

        #region State Machine

        /// <summary>
        /// 同步远程终端
        /// </summary>
        public void Sync()
        {
            uint now = Platform.GetCurrentTimeMS();

            if (Type == Device.DeviceType.Remote)
            {
                uint interval = _syncRoundtripsRemaining == NUM_SYNC_ROUNDTRIPS ? SYNC_FIRST_RETRY_INTERVAL : SYNC_NEXT_RETRY_INTERVAL;
                if (_lastSendTime + interval < now)
                {
                    _syncRoundtripsRandomRequest = Platform.GetRandomUnsignedShort();
                    SendMessage(new DeviceSyncMessage { DeviceId = Id, PlayerCount = PlayerCount, RandomRequest = _syncRoundtripsRandomRequest });
                }
            }
        }

        /// <summary>
        /// 开始远端同步
        /// </summary>
        public void StartSyncing()
        {
            _syncRoundtripsRemaining = NUM_SYNC_ROUNDTRIPS;
            _syncRoundtripsRandomRequest = Platform.GetRandomUnsignedShort();
            SendMessage(new DeviceSyncMessage { DeviceId = Id, PlayerCount = PlayerCount, RandomRequest = _syncRoundtripsRandomRequest });
        }

        /// <summary>
        /// 完成远端同步
        /// </summary>
        public void FinishedSyncing()
        {
            State = DeviceState.Running;
            //var ev = new DeviceSyncedEvent { DeviceId = Id };
            //_session.AddSessionEvent(ev);
        }

        #endregion

        #region Sending and Receiving messages
        /// <summary>
        /// 发送消息到远端
        /// </summary>
        /// <param name="message"></param>
        public void SendMessage(DeviceMessage message)
        {
            _lastSendTime = Platform.GetCurrentTimeMS();
            _sendQueue.Enqueue(new DeviceMessageQueueEntry { Time = _lastSendTime, Message = message });
            PumpSendQueue();
        }

        /// <summary>
        /// 处理远端发来的信息
        /// </summary>
        /// <param name="message"></param>
        public void HandleMessage(DeviceMessage message)
        {
            switch (message)
            {
                case DeviceSyncMessage syncMessage:
                    Debug.Assert(syncMessage.PlayerCount == _session.AllDevices[syncMessage.DeviceId].PlayerCount);
                    SendMessage(new DeviceSyncConfirmMessage { DeviceId = Id, PlayerCount = PlayerCount, RandomResponse = syncMessage.RandomRequest });
                    break;
                case DeviceSyncConfirmMessage syncConfirmMessage:
                    Debug.Assert(syncConfirmMessage.PlayerCount == _session.AllDevices[syncConfirmMessage.DeviceId].PlayerCount);
                    if (syncConfirmMessage.RandomResponse == _syncRoundtripsRandomRequest)
                    {
                        _syncRoundtripsRemaining -= 1;
                        if (_syncRoundtripsRemaining > 0)
                        {
                            _syncRoundtripsRandomRequest = Platform.GetRandomUnsignedShort();
                            SendMessage(new DeviceSyncMessage { DeviceId = Id, PlayerCount = PlayerCount, RandomRequest = _syncRoundtripsRandomRequest });
                        }
                        else
                        {
                            FinishedSyncing();
                        }
                    }
                    break;
                case DeviceInputMessage inputMessage:
                    _session.AddRemoteInput(Id, inputMessage);
                    UpdateAckedInputFrame(inputMessage);
                    break;
                case DeviceInputAckMessage inputAckMessage:
                    UpdateAckedInputFrame(inputAckMessage);
                    break;
            }
        }

        /// <summary>
        /// 更新确认帧
        /// </summary>
        /// <param name="inputAckMessage">确认信息</param>
        private void UpdateAckedInputFrame(DeviceInputAckMessage inputAckMessage)
        {
            if (LastAckedInputFrame + 1 == inputAckMessage.Frame)
            {
                LastAckedInputFrame = inputAckMessage.Frame;
            }
        }

        /// <summary>
        /// 更新确认帧
        /// </summary>
        /// <param name="inputAckMessage">确认信息</param>
        private void UpdateAckedInputFrame(DeviceInputMessage inputMessage)
        {
            for (uint i = inputMessage.StartFrame; i <= inputMessage.EndFrame; i++)
            {
                if (LastAckedInputFrame + 1 == i)
                    LastAckedInputFrame = i;
            }
        }

        /// <summary>
        /// 即可发送信息
        /// </summary>
        private void PumpSendQueue()
        {
            while (_sendQueue.Count > 0)
            {
                _session.SendMessageTo(Id, _sendQueue.Dequeue().Message);
            }
        }

        #endregion
    }

    internal class DeviceMessageQueueEntry
    {
        public uint Time;
        public DeviceMessage Message;
    }
}
